import traceback
from datetime import datetime

from configs import config
from database.base import db
from models.blog import ArticleModel
from proto import blog_pb2, blog_pb2_grpc

from loguru import logger

from utils import status_code


class BlogService(blog_pb2_grpc.BlogService):

    @staticmethod
    def _format_page(request):
        page = request.page or config.DEFAULT_PAGE
        page_size = request.pageSize or config.DEFAULT_PAGE
        return page, page_size

    @staticmethod
    def _get_model_fields(model, resp):
        for fields in model.columns():
            v = getattr(model, fields)
            if hasattr(resp, fields) and v:
                if isinstance(v, datetime):
                    setattr(resp, fields, str(v))
                else:
                    setattr(resp, fields, v)

    @staticmethod
    def response(resp, status=status_code.SUCCESS, msg='success', data=None):
        resp.status = status
        resp.msg = msg
        logger.info(f"will response, now resp obj: {resp}")
        return resp

    def _gen_list_data(self, src_model_list, resp_obj):
        for model in src_model_list:
            resp = resp_obj()
            self._get_model_fields(model, resp)
            yield resp

    def ArticleList(self, request, context, *args, **kwargs):
        logger.info(f'get article req: {request}')
        resp = blog_pb2.ArticleListResponse()

        page, page_size = self._format_page(request)
        articles, resp.total = ArticleModel.get_page_data(page, page_size)

        resp.data.extend(list(self._gen_list_data(articles, blog_pb2.ArticleInfo)))
        return resp

    def ArticleUpdate(self, request, context, *args, **kwargs):
        logger.info(f"get blog update req: {request}")
        resp = blog_pb2.ArticleDetailResponse()
        article = article_op.get_blog_by_id(request)
        if not article:
            return self.response(resp, status_code.ARTICLE_NOTFOUND, msg='not found')
        else:
            article_op.update(article, request)
            self._get_model_fields(article, resp.data)
            return self.response(resp, status_code.SUCCESS, msg='success')

    def CreateArticle(self, request, context, *args, **kwargs):
        logger.info(f"get create article req: {request}")
        resp = blog_pb2.ArticleDetailResponse()
        try:
            article = article_op.add(request)
        except Exception:
            logger.error(f"create article error, msg: {traceback.format_exc()}")
            return self.response(resp, status_code.CREATE_FAIL, msg='fail')
        else:
            self._get_model_fields(article, resp.data)
            return self.response(resp, status_code.SUCCESS, msg='success')


class ArticleOperation:

    @staticmethod
    def _get_req_data(request, fields):
        return {i: getattr(request, i) for i in fields if hasattr(request, i) and getattr(request, i)}

    @staticmethod
    def _add(instance, data):
        instance.set_attr(data)
        return instance

    @staticmethod
    def _set(instance, data):
        instance.set_attr(data)
        return instance

    def add(self, request):
        article = ArticleModel()
        data = self._get_req_data(request, article.columns())
        logger.info(f"before save to db, generate data: {data}")
        return self.do_with_db('_add', article, data)

    @staticmethod
    def get_blog_by_id(request):
        query = {'key': 'id', 'value': request.id}
        article = ArticleModel.get_one(query)
        logger.info(f"get article: {article} by {query}")
        return article

    def update(self, article, request):
        data = self._get_req_data(request, article.columns())
        logger.info(f"before update article: {article.id}-{article.title}, generate data: {data}")
        return self.do_with_db('_set', article, data)

    def do_with_db(self, func, *args, **kwargs):
        with db.auto_commit():
            instance = getattr(self, func)(*args, **kwargs)
            db.add(instance)
        return instance


article_op = ArticleOperation()
